﻿using System;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Algorithm;
using YJKGridTopologyCal.Mathematics;

namespace YJKGridTopologyCal.Operation.Distance3D
{
	public class PlanarPolygon3D
	{
		public PlanarPolygon3D(IPolygon poly)
		{
			this._poly = poly;
			this._plane = PlanarPolygon3D.FindBestFitPlane(poly);
			this._facingPlane = this._plane.ClosestAxisPlane();
		}

		private static Plane3D FindBestFitPlane(IPolygon poly)
		{
			ICoordinateSequence coordinateSequence = poly.ExteriorRing.CoordinateSequence;
			Coordinate basePt = PlanarPolygon3D.AveragePoint(coordinateSequence);
			return new Plane3D(PlanarPolygon3D.AverageNormal(coordinateSequence), basePt);
		}

		private static Vector3D AverageNormal(ICoordinateSequence seq)
		{
			int count = seq.Count;
			Coordinate coordinate = new Coordinate(0.0, 0.0, 0.0);
			Coordinate coordinate2 = new Coordinate(0.0, 0.0, 0.0);
			Coordinate coordinate3 = new Coordinate(0.0, 0.0, 0.0);
			for (int i = 0; i < count - 1; i++)
			{
				seq.GetCoordinate(i, coordinate2);
				seq.GetCoordinate(i + 1, coordinate3);
				coordinate.X += (coordinate2.Y - coordinate3.Y) * (coordinate2.Z + coordinate3.Z);
				coordinate.Y += (coordinate2.Z - coordinate3.Z) * (coordinate2.X + coordinate3.X);
				coordinate.Z += (coordinate2.X - coordinate3.X) * (coordinate2.Y + coordinate3.Y);
			}
			coordinate.X /= (double)count;
			coordinate.Y /= (double)count;
			coordinate.Z /= (double)count;
			return Vector3D.Create(coordinate).Normalize();
		}

		private static Coordinate AveragePoint(ICoordinateSequence seq)
		{
			Coordinate coordinate = new Coordinate(0.0, 0.0, 0.0);
			int count = seq.Count;
			for (int i = 0; i < count; i++)
			{
				coordinate.X += seq.GetOrdinate(i, Ordinate.X);
				coordinate.Y += seq.GetOrdinate(i, Ordinate.Y);
				coordinate.Z += seq.GetOrdinate(i, Ordinate.Z);
			}
			coordinate.X /= (double)count;
			coordinate.Y /= (double)count;
			coordinate.Z /= (double)count;
			return coordinate;
		}

		public Plane3D Plane
		{
			get
			{
				return this._plane;
			}
		}

		public IPolygon Polygon
		{
			get
			{
				return this._poly;
			}
		}

		public bool Intersects(Coordinate intPt)
		{
			if (Location.Exterior == this.Locate(intPt, this._poly.ExteriorRing))
			{
				return false;
			}
			for (int i = 0; i < this._poly.NumInteriorRings; i++)
			{
				if (this.Locate(intPt, this._poly.GetInteriorRingN(i)) == Location.Interior)
				{
					return false;
				}
			}
			return true;
		}

		private Location Locate(Coordinate pt, ILineString ring)
		{
			ICoordinateSequence ring2 = PlanarPolygon3D.Project(ring.CoordinateSequence, this._facingPlane);
			return RayCrossingCounter.LocatePointInRing(PlanarPolygon3D.Project(pt, this._facingPlane), ring2);
		}

		public bool Intersects(Coordinate pt, ILineString ring)
		{
			ICoordinateSequence ring2 = PlanarPolygon3D.Project(ring.CoordinateSequence, this._facingPlane);
			Coordinate p = PlanarPolygon3D.Project(pt, this._facingPlane);
			return Location.Exterior != RayCrossingCounter.LocatePointInRing(p, ring2);
		}

		private static ICoordinateSequence Project(ICoordinateSequence seq, Plane facingPlane)
		{
			if (facingPlane == YJKGridTopologyCal.Mathematics.Plane.XY)
			{
				return AxisPlaneCoordinateSequence.ProjectToXY(seq);
			}
			if (facingPlane != YJKGridTopologyCal.Mathematics.Plane.XZ)
			{
				return AxisPlaneCoordinateSequence.ProjectToYZ(seq);
			}
			return AxisPlaneCoordinateSequence.ProjectToXZ(seq);
		}

		private static Coordinate Project(Coordinate p, Plane facingPlane)
		{
			if (facingPlane == YJKGridTopologyCal.Mathematics.Plane.XY)
			{
				return new Coordinate(p.X, p.Y);
			}
			if (facingPlane != YJKGridTopologyCal.Mathematics.Plane.XZ)
			{
				return new Coordinate(p.Y, p.Z);
			}
			return new Coordinate(p.X, p.Z);
		}

		private readonly Plane3D _plane;

		private readonly IPolygon _poly;

		private readonly Plane _facingPlane = YJKGridTopologyCal.Mathematics.Plane.Undefined;
	}
}
